/*=========================================================================
 *
 *  Copyright SINAPSE: Scalable Informatics for Neuroscience, Processing and Software Engineering
 *            The University of Iowa
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
/*
 * Author: Hans J. Johnson, Wei Lu
 * at Psychiatry Imaging Lab,
 * University of Iowa Health Care 2010
 */

#ifndef landmarksConstellationTrainingDefinitionIO_h
#define landmarksConstellationTrainingDefinitionIO_h
#include <vector>
#include <iostream>
#include <fstream>
#include <string>
#include <itkPoint.h>
#include <itkContinuousIndex.h>
#include "landmarksDataSet.h"
#include "landmarksConstellationModelBase.h"
#include "itkNumberToString.h"


// HACK:  Remove the multiple inheritance here.
class landmarksConstellationTrainingDefinitionIO : public landmarksConstellationModelBase,
  public std::vector<landmarksDataSet>   // This should be a private member
                                         // variable
{
private:
  using err_flags = enum

  {

    eof,

    bad

  };

public:
  landmarksConstellationTrainingDefinitionIO() = default;

  explicit landmarksConstellationTrainingDefinitionIO(const std::string & filename) { this->ReadFile(filename); }

  const ValMapType &
  GetRadii() const override
  {
    return landmarksConstellationModelBase::GetRadii();
  }

  template <typename type>
  void
  Read(std::ifstream & s, type & var)
  {
    if (s.bad())
    {
      throw landmarksConstellationTrainingDefinitionIO::bad;
    }
    if (s.eof())
    {
      throw landmarksConstellationTrainingDefinitionIO::eof;
    }
    s >> var;
  }

  int
  ReadFile(const std::string & filename)
  {
    std::ifstream input(filename.c_str());

    if (input.bad())
    {
      return -1;
    }
    try
    {
      this->Read<unsigned int>(input, this->m_NumDataSets);
      // Note, more than 2 datasets are needed in order to get valid sample
      // means and variances.
      if (m_NumDataSets < 2)
      {
        itkGenericExceptionMacro(<< "NumberOfDatasets must be greater than 2");
      }
      this->Read<unsigned int>(input, this->m_SearchboxDims);
      if (m_SearchboxDims % 2 == 0)
      {
        itkGenericExceptionMacro(<< "SearchBoxDims must be odd");
      }
      this->Read<float>(input, this->m_ResolutionUnits);
      if (m_ResolutionUnits <= 0)
      {
        itkGenericExceptionMacro(<< "m_ResolutionUnits must be greater than zero");
      }
      this->Read<float>(input, this->m_InitialRotationAngle);
      this->Read<float>(input, this->m_InitialRotationStep);
      this->Read<unsigned int>(input, this->m_NumRotationSteps);

      // Read in template size for each landmark
      std::string name;
      float       val = 0;
      this->Read<std::string>(input, name);

      while (name.compare("END") != 0)
      {
        this->Read<float>(input, val);
        this->m_Radius[name] = val;
        this->Read<float>(input, val);
        this->m_Height[name] = val;
        this->Read<std::string>(input, name);
      }
      // Read in landmarks
      for (unsigned int i = 0; i < this->m_NumDataSets; i++)
      {
        std::string TrainingImageFilename;
        this->Read<std::string>(input, TrainingImageFilename);

        std::string LandmarksFilename;
        this->Read<std::string>(input, LandmarksFilename);

        landmarksDataSet DataSet(TrainingImageFilename, LandmarksFilename);
        this->push_back(DataSet);

        std::string Finished;
        this->Read<std::string>(input, Finished);
        // This separator line says 'END'
      }
      input.close();
    }
    catch (err_flags f)
    {
      std::cerr << "File read error "
                << (f == landmarksConstellationTrainingDefinitionIO::eof ? "unexpected end of file" : "file read error")
                << std::endl;
      std::cerr.flush();
      input.close();
      return -1;
    }
    return 0;
  }

private:
};

inline std::ostream &
operator<<(std::ostream & os, const landmarksConstellationTrainingDefinitionIO & def)
{
  itk::NumberToString<double> doubleToString;

  os << def.GetNumDataSets() << std::endl;
  os << def.GetSearchboxDims() << " " << def.GetResolutionUnits() << std::endl;
  os << doubleToString(def.GetRadius("RP")) << " " << doubleToString(def.GetHeight("RP")) << std::endl;
  os << doubleToString(def.GetRadius("AC")) << " " << doubleToString(def.GetHeight("AC")) << std::endl;
  os << doubleToString(def.GetRadius("PC")) << " " << doubleToString(def.GetHeight("PC")) << std::endl;
  os << doubleToString(def.GetRadius("VN4")) << " " << doubleToString(def.GetHeight("VN4")) << std::endl;
  os << doubleToString(def.GetInitialRotationAngle()) << " " << def.GetInitialRotationStep() << " "
     << def.GetNumRotationSteps() << std::endl
     << std::endl;
  for (unsigned int i = 0; i < def.GetNumDataSets(); i++)
  {
    os << def[i] << std::endl;
  }
  return os;
}

#endif // landmarksConstellationTrainingDefinitionIO_h
